/***************************************************
File:           LPK_WindField.cs
Authors:        Christopher Onorati
Last Updated:   5/20/2019
Last Version:   2018.3.14

Description:
  This component causes objects within its collider to be
  blown away like the wind, or pulled in like a vortex.

This script is a basic and generic implementation of its 
functionality. It is designed for educational purposes and 
aimed at helping beginners.

Copyright 2018-2019, DigiPen Institute of Technology
***************************************************/

using UnityEngine;
using UnityEditor;

namespace LPK
{

/**
* CLASS NAME  : LPK_MagneticField
* DESCRIPTION : Wind field which pushes objects away.
**/
[RequireComponent(typeof(Transform), typeof(BoxCollider2D))]
public class LPK_WindField : LPK_Component
{
    /************************************************************************************/

    public bool m_bConstantForce = false;

    public float m_flMagnitude = 10.0f;

    [Tooltip("Tags that the GameObjects must have to be affected. Using this is much less expensive.  If not set, any GameObject with a Rigidbody will be affected.")]
    [TagDropdown]
    public string[] m_SearchTags;

    /************************************************************************************/

    float m_flFieldSize;

    /************************************************************************************/

    BoxCollider2D m_cBoxCollider2D;

    /**
    * FUNCTION NAME: OnStart
    * DESCRIPTION  : Sets up event listening.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    override protected void OnStart()
    {
        m_cBoxCollider2D = GetComponent<BoxCollider2D>();

        m_flFieldSize = m_cBoxCollider2D.size.y;

        //Ensure the collider is a trigger or this will not work.
        if(!m_cBoxCollider2D.isTrigger)
        {
            m_cBoxCollider2D.isTrigger = true;

            if (m_bPrintDebug)
                LPK_PrintDebug(this, "WindField was not set to have a trigger collider.  Switching automatically.  Please resolve in editor!");
        }
    }

    /**
    * FUNCTION NAME: Update
    * DESCRIPTION  : Gets the size of the box every frame in case something modifies it.  This is really sub-optimal but the only way to
    *                gaurantee this is getting set correclty with user-created events and scripts.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void Update()
    {
        m_flFieldSize = m_cBoxCollider2D.size.y;
    }

    /**
    * FUNCTION NAME: OnTriggerStay2D
    * DESCRIPTION  : Manages the pushing of detected objects.
    * INPUTS       : col - Object to apply force to.
    * OUTPUTS      : None
    **/
    void OnTriggerStay2D(Collider2D col)
    {
        if (col.gameObject.GetComponent<Rigidbody2D>() != null)
        {
            if (m_SearchTags.Length == 0)
                PushObject(col.gameObject);
            else
            {
                for (int i = 0; i < m_SearchTags.Length; i++)
                {
                    //Go through the LPK Tag manager before doing a direct check.
                    if(col.gameObject.GetComponent<LPK_MultiTagManager>())
                    {
                        if(col.gameObject.GetComponent<LPK_MultiTagManager>().CheckForTag(m_SearchTags[i]))
                            PushObject(col.gameObject);
                    }
                    else if (col.gameObject.tag == m_SearchTags[i])
                        PushObject(col.gameObject);
                }
            }
        }
    }

    /**
    * FUNCTION NAME: PushObject
    * DESCRIPTION  : Manages the pushing of detected objects.
    * INPUTS       : _target - Object to apply force to.
    * OUTPUTS      : None
    **/
    void PushObject(GameObject _target)
    {
        Rigidbody2D tarRigidBody = _target.GetComponent<Rigidbody2D>();

        //Get the right axis of the GameObject.
        Vector3 lineDir = transform.right.normalized;

        //Get direction from target to the game object.
        Vector3 direction = _target.transform.position - transform.position;

        //Get the dot product of the direction onto the line.
        float dot = Vector3.Dot(direction, lineDir);

        //Get the nearest point from the target to the line.
        Vector3 nearestPoint = transform.position + lineDir * dot;

        //Correct direction to be between the target and the nearest point, instead of the target and the point of this GameObject.
        direction = _target.transform.position - nearestPoint;

        float distanceScalar = 1.0f;

        if (!m_bConstantForce)
        {
            float distance = Vector3.Distance(_target.transform.position, nearestPoint);
            distanceScalar = Mathf.Clamp(1.0f - (distance / m_flFieldSize), 0.0f, 1.0f);
        }

        tarRigidBody.AddForce(m_flMagnitude * direction.normalized * distanceScalar * (1/ Time.smoothDeltaTime));

        if (m_bPrintDebug)
            LPK_PrintDebug(this, "Pushing object:" + _target.name);
    }
}

#if UNITY_EDITOR

[CustomEditor(typeof(LPK_WindField))]
public class LPK_WindFieldEditor : Editor
{
    SerializedProperty searchTags;

    /**
    * FUNCTION NAME: OnEnable
    * DESCRIPTION  : Save out serialized classes.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void OnEnable()
    {
        searchTags = serializedObject.FindProperty("m_SearchTags");
    }

    /**
    * FUNCTION NAME: OnInspectorGUI
    * DESCRIPTION  : Override GUI for inspector.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    public override void OnInspectorGUI()
    {
        LPK_WindField owner = (LPK_WindField)target;

        LPK_WindField editorOwner = owner.GetComponent<LPK_WindField>();

        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Script");
        editorOwner = (LPK_WindField)EditorGUILayout.ObjectField(editorOwner, typeof(LPK_WindField), false);
        GUILayout.EndHorizontal();
        EditorGUI.EndDisabledGroup();

        //Undo saving.
        Undo.RecordObject(owner, "Property changes on LPK_WindField");

        //Component properties.
        GUILayout.Space(10);
        EditorGUILayout.LabelField("Component Properties", EditorStyles.boldLabel);

        owner.m_bConstantForce = EditorGUILayout.Toggle(new GUIContent("Constant Force", "Set the field to apply a constant force, rather than be scaled based on distance."), owner.m_bConstantForce);
        owner.m_flMagnitude = EditorGUILayout.FloatField(new GUIContent("Magnitude", "Magnitude of the force.  Positive forces repel objects, negative forces pull objects."), owner.m_flMagnitude);
        EditorGUILayout.PropertyField(searchTags, true);

        //Debug properties.
        GUILayout.Space(10);
        EditorGUILayout.LabelField("Debug Properties", EditorStyles.boldLabel);

        owner.m_bPrintDebug = EditorGUILayout.Toggle(new GUIContent("Print Debug Info", "Toggle console debug messages."), owner.m_bPrintDebug);
        owner.m_sLabel = EditorGUILayout.TextField(new GUIContent("Label", "Notes for the user about this component.  This does nothing to the game or build."), owner.m_sLabel);

        //Apply changes.
        serializedObject.ApplyModifiedProperties();
    }
}

#endif  //UNITY_EDITOR

}   //LPK
